package edu.pitt.cs.cs2410pj2.core;

import java.io.IOException;
import java.util.ArrayList;

import edu.pitt.cs.cs2410pj2.datamodel.Message;
import edu.pitt.cs.cs2410pj2.datamodel.MessageBus;
import edu.pitt.cs.cs2410pj2.datamodel.MessageType;
import edu.pitt.cs.cs2410pj2.datamodel.State;
import edu.pitt.cs.cs2410pj2.util.AddrProcessor;
import edu.pitt.cs.cs2410pj2.util.Parameters;
import edu.pitt.cs.cs2410pj2.util.TheLogger;
import edu.pitt.cs.cs2410pj2.util.Trace;

/**
 * The class of a tile, contains a CPU, one L1 cache, one L2 cache and a
 * directory
 * 
 * @author Wenchen Wang, zhangfan
 * 
 */
public class Tile {
	private Parameters para;
	private ArrayList<Trace> traces = new ArrayList<Trace>();
	private L1Cache l1Cache;
	private MessageBus bus;
	private L2Controller controller;
	private int tileId;
	private Trace currentTrace = null;
	private ArrayList<Integer> sharers;

	// TheLogger logger;//JUST FOR TEMP TESTING, REMOVE IT LATER

	// default constructor
	public Tile() {

	}

	// For statistics
	int hitCount = 0;
	int missCount = 0;
	int controlMsgCount = 0;
	int dataMsgCount = 0;

	public double getHitMissRate() {
		return hitCount * 1.0 / missCount;
	}

	public int getHit() {
		return hitCount;
	}

	public int getMissCount() {
		return missCount;
	}

	public String getMsgInfo() {
		String msg = "# of control messages:" + controlMsgCount;
		msg += ",# of data messages:" + dataMsgCount;
		return msg;
	}

	public void showLog(TheLogger logger) throws IOException {
		// this.logger = logger;
		logger.log("----------------");
		logger.log("TILE: " + tileId);
		logger.log("HIT:" + hitCount + ",MISS:" + missCount);
		/*
		 * logger.log("TRACE OF TILE " + tileId); for (int i = 0; i <
		 * traces.size(); i++) { logger.log(traces.get(i).toString()); }
		 */
		if (currentTrace != null) {
			logger.log("CURRENT TRACE:" + currentTrace.toString());
		}
		logger.log(l1Cache.toString());
		logger.log(controller.toString());
		logger.log("----------------");
	}

	public Tile(Parameters para, int tileId) {
		this.para = para;
		this.tileId = tileId;
		l1Cache = new L1Cache(para, tileId);
		controller = new L2Controller(para, tileId);
	}

	public MessageBus getBus() {
		return bus;
	}

	public void setBus(MessageBus bus) {
		this.bus = bus;
	}

	public void addTrace(Trace trace) {
		if (currentTrace == null) {
			currentTrace = trace;
		}
		this.traces.add(trace);
	}

	// MessageBus uses this method to inform the Tile the coming new message
	// The main logic for handling the different cases here, do different
	// processing when type comes
	// return false if there is a delay or something, otherwise returns true
	public boolean receiveMsg(Message msg, int timeCycle) {
		if (!msg.isSystem() && !msg.isWaiting()) {
			if (msg.isData()) {
				dataMsgCount++;
			} else {
				if(!msg.isUseless())
					controlMsgCount++;
			}
		}
		// Put the logics here
		if (msg.getType() == MessageType.TRACEMSG) {
			trace(msg, timeCycle);
			return true;
		} else if (msg.getType() == MessageType.REDOREQ) {
			if(currentTrace.isFinished()) {
				return false;
			} else {
				currentTrace.setIssued(false, timeCycle);
				return true;
			}
		} else if (msg.getType() == MessageType.WRITEMISS) {

			// check whether local l2Cache has that block
			String addr = msg.getBlockAddr();
			if (controller.isBlockCached(addr)) {
				// check whether the block is busy
				if (controller.isBlockBusy(msg.getBlockAddr())) {
					return false;
				}
				// If l2Cache has that block
				// check the block directory status
				int state = controller.getBlockState(addr);
				l2HasBLockWriteMiss(msg, state, timeCycle);
				return true;
			} else {
				// If l2Cache does not have that block
				if (!msg.isWaiting()) {
					l2NoBlockWriteMiss(msg, addr);
				}
				return false;
			}
		} else if (msg.getType() == MessageType.READMISS) {
			// check whether local l2Cache has that block
			String addr = msg.getBlockAddr();
			if (controller.isBlockCached(addr)) {
				// check whether the block is busy
				if (controller.isBlockBusy(msg.getBlockAddr())) {
					return false;
				}
				// If l2Cache has that block
				// check the block directory status
				int state = controller.getBlockState(addr);
				l2HasBlockReadMiss(state, addr, msg);
				return true;
			} else {
				// if l2Cache does not have that block
				if (!msg.isWaiting()) {
					l2NoBlockReadMiss(addr, msg);
				}
				return false;
			}

		} else if (msg.getType() == MessageType.WRITHIT) {
			if (!controller.isBlockCached(msg.getBlockAddr())) {
				// When there is a write hit, and the L2 does not have the block
				// This shows that the block has been removed, it should have
				// sent a reply to others to invalidate their blocks, so ask the
				// requester to redo the request
				Message message = new Message();
				message.setType(MessageType.REDOREQ);
				message.setIsData(false);
				message.setFrom(this.tileId);
				message.setTo(msg.getFrom());
				bus.sendMessage(message);
				return true;

			}
			// check whether the block is busy
			if (controller.isBlockBusy(msg.getBlockAddr())) {
				return false;
			}
			String blockAddr = msg.getBlockAddr();
			writHit(blockAddr, msg);
			return true;
		} else if (msg.getType() == MessageType.INVALIDATE) {
			String blockAddr = msg.getBlockAddr();
			invalidate(msg, blockAddr);
			return true;
		} else if (msg.getType() == MessageType.INVALIDATEFROMLRU) {
			String blockAddr = msg.getBlockAddr();
			blockInvalidate(msg, blockAddr);
			return true;
		} else if (msg.getType() == MessageType.BLOCKFETCH) {
			return fetchBlock(msg);
			// return true;
		} else if (msg.getType() == MessageType.BLOCKREPLY) {
			replyBlock(msg, timeCycle);
			return true;
		} else if (msg.getType() == MessageType.DATAWRITEBACK) {
			writeBack(msg, timeCycle);
			return true;
		} else if (msg.getType() == MessageType.L2LOAD) {
			loadL2(msg, timeCycle);
			return true;
		} else if (msg.getType() == MessageType.TILEREPLY) {
			sendRequestToL1(msg,timeCycle);
			return true;
		} else if (msg.getType() == MessageType.LISTREPLY) {

			replyInvalidList(msg, timeCycle);
			return true;
		} else if (msg.getType() == MessageType.CHANGESTATE) {
			changeHomeState(msg, timeCycle);
			return true;
		} else if (msg.getType() == MessageType.SHAREBLOCKREPLY) {
			shareBlockReply(msg, timeCycle);
		} else if (msg.getType() == MessageType.ACKNOWLEDGEMENT) {
			receiveAck(msg, timeCycle);
		}
		return true;
	}

	public void trace(Message msg, int timeCycle) {
		if (currentTrace.isTypeRead() == true) {
			int processL1 = currentTrace.getCordId();
			boolean isInL1Read = l1Cache.isBlockCached(currentTrace.getAddr());
			if (isInL1Read) {
				// do nothing
				l1Cache.updateTime(currentTrace.getAddr(), timeCycle);
				hitCount++;
				currentTrace.setFinished(true, timeCycle);

				// Remove it later
				// try {
				// logger.log(currentTrace.toString());
				// } catch (IOException e) {
				// // TODO Auto-generated catch block
				// e.printStackTrace();
				// }

			} else {
				// send READMISS message
				Message message = new Message();
				message.setBlockAddr(currentTrace.getAddr());
				message.setIsData(false);
				message.setType(MessageType.READMISS);
				message.setFrom(getTileId());
				message.setTo(AddrProcessor.processAddr(currentTrace.getAddr(),
						para.p, para.n2, para.a2, para.b).getTileId());
				getBus().sendMessage(message);
				missCount++;
			}
		} else {
			boolean isInL1Write = l1Cache.isBlockCached(currentTrace.getAddr());
			if (isInL1Write) {
				hitCount++;
				if (l1Cache.findBlock(currentTrace.getAddr()).getState() == State.MODIFIED) {
					// do nothing
					l1Cache.updateTime(currentTrace.getAddr(), timeCycle);
					currentTrace.setFinished(true, timeCycle);

					// Remove it later
					// try {
					// logger.log(currentTrace.toString());
					// } catch (IOException e) {
					// // TODO Auto-generated catch block
					// e.printStackTrace();
					// }
				} else {
					// send WRITEHIT message
					Message message = new Message();
					message.setBlockAddr(currentTrace.getAddr());
					message.setIsData(false);
					message.setType(MessageType.WRITHIT);
					message.setFrom(getTileId());
					message.setTo(AddrProcessor.processAddr(
							currentTrace.getAddr(), para.p, para.n2, para.a2,
							para.b).getTileId());
					getBus().sendMessage(message);
				}
			} else {
				missCount++;
				// send WRITEMISS message
				Message message = new Message();
				message.setBlockAddr(currentTrace.getAddr());
				message.setIsData(false);
				message.setType(MessageType.WRITEMISS);
				message.setFrom(getTileId());
				message.setTo(AddrProcessor.processAddr(currentTrace.getAddr(),
						para.p, para.n2, para.a2, para.b).getTileId());
				getBus().sendMessage(message);
			}
		}
	}

	public void l2HasBLockWriteMiss(Message msg, int state, int timeCycle) {
		String blockAddr = msg.getBlockAddr();
		if (state == State.INVALID) {
			// set busy bit to 1
			controller.setBlockBusy(blockAddr, true);
			controller.updateBlockTime(blockAddr, timeCycle);
			// send BLOCKREPLY message to L1
			Message message = new Message();
			message.setBlockAddr(blockAddr);
			message.setData(controller.findBlock(blockAddr).getData());
			message.setIsData(true);
			message.setTimeOnRoad(para.d);
			message.setType(MessageType.BLOCKREPLY);
			message.setFrom(getTileId());
			message.setTo(msg.getFrom());
			getBus().sendMessage(message);
		} else if (state == State.MODIFIED) {
			// set busy bit to 1
			controller.setBlockBusy(blockAddr, true);
			controller.updateBlockTime(blockAddr, timeCycle);
			// send TILEREPLY message to L1
			Message message = new Message();
			message.setBlockAddr(blockAddr);
			message.setData(controller.getOwnersList(blockAddr) + ","
					+ "INVALID");
			message.setIsData(false);
			message.setType(MessageType.TILEREPLY);
			message.setFrom(getTileId());
			message.setTo(msg.getFrom());
			getBus().sendMessage(message);
			
			
			//handle extra case, someone requests a block but controller says you have it
			if(controller.getOwners(blockAddr).size()==1&&controller.getOwners(blockAddr).get(0)==msg.getFrom()) {
				if(controller.isBlockBusy(blockAddr)) {
					controller.setBlockBusy(blockAddr, false);
				}
				controller.loadData(blockAddr, tileId, bus);
			}
			controller.clearOwners(blockAddr);

		} else if (state == State.SHARED) {
			// set busy bit to 1
			controller.setBlockBusy(blockAddr, true);
			controller.updateBlockTime(blockAddr, timeCycle);

			// send BLOCKREPLY message to (block) l1
			Message message1 = new Message();
			message1.setBlockAddr(blockAddr);
			message1.setData(controller.findBlock(blockAddr).getData());
			message1.setIsData(true);
			message1.setType(MessageType.SHAREBLOCKREPLY);
			message1.setTimeOnRoad(para.d);
			message1.setFrom(getTileId());
			message1.setTo(msg.getFrom());
			getBus().sendMessage(message1);
			// send LISTREPLY message (list of sharers) to l1
			Message message2 = new Message();
			message2.setBlockAddr(blockAddr);
			message2.setData(controller.getOwnersList(blockAddr));
			message2.setIsData(false);
			message2.setType(MessageType.LISTREPLY);
			message2.setFrom(getTileId());
			message2.setTo(msg.getFrom());
			getBus().sendMessage(message2);
			
			//something wrong, reload
			if(controller.getOwners(blockAddr).size()==0) {
				controller.loadData(blockAddr, timeCycle, bus);
			}
			// clear ownerList
			controller.clearOwners(blockAddr);
		}
	}

	public void l2NoBlockWriteMiss(Message msg, String blockAddr) {
		// send L2LOAD message to memory
		// change directory state to "invalid"
		controller.loadData(blockAddr, tileId, getBus());
		// controller.changeBlockState(blockAddr, State.INVALID);
		// l2HasBLockWriteMiss(msg, State.INVALID, blockAddr);
	}

	public void l2HasBlockReadMiss(int state, String blockAddr, Message msg) {
		if (state == State.INVALID || state == State.SHARED) {
			// change state to "shared"
			controller.changeBlockState(blockAddr, State.SHARED);
			controller.addOwner(blockAddr, msg.getFrom());
			// send BLOCKREPLY message to L1
			Message message = new Message();
			message.setBlockAddr(blockAddr);
			message.setData(controller.findBlock(blockAddr).getData());
			message.setUseless(true);
			message.setIsData(true);
			message.setTimeOnRoad(para.d);
			message.setType(MessageType.BLOCKREPLY);
			message.setFrom(getTileId());
			message.setTo(msg.getFrom());
			getBus().sendMessage(message);
		} else if (state == State.MODIFIED) {
			// send TILEREPLY message to l1
			Message message = new Message();
			message.setBlockAddr(blockAddr);
			message.setData(controller.getOwnersList(blockAddr) + ",SHARED");
			message.setIsData(false);
			message.setType(MessageType.TILEREPLY);
			message.setFrom(getTileId());
			message.setTo(msg.getFrom());
			getBus().sendMessage(message);
			
			//handle extra case, someone requests a block but controller says you have it
			if(controller.getOwners(blockAddr).size()==1&&controller.getOwners(blockAddr).get(0)==msg.getFrom()) {
				if(controller.isBlockBusy(blockAddr)) controller.setBlockBusy(blockAddr, false);
				controller.loadData(blockAddr, tileId, bus);
			}
		}
	}

	public void l2NoBlockReadMiss(String blockAddr, Message msg) {
		// send L2LOAD message to memory
		// change directory state to "invalid"
		controller.loadData(blockAddr, tileId, getBus());
		// controller.changeBlockState(blockAddr, State.INVALID);
		// l2HasBlockReadMiss(State.INVALID, blockAddr, msg);
	}

	public void writHit(String blockAddr, Message msg) {
		// set busy bit to 1
		controller.setBlockBusy(blockAddr, true);

		// // send SHAREBLOCKREPLY message to L1
		// Message message = new Message();
		// message.setBlockAddr(blockAddr);
		// message.setData(controller.findBlock(blockAddr).getData());
		// message.setIsData(true);
		// message.setTimeOnRoad(para.d);
		// message.setType(MessageType.SHAREBLOCKREPLY);
		// message.setFrom(getTileId());
		// message.setTo(msg.getFrom());
		// getBus().sendMessage(message);

		// send LISTREPLY message to L1
		Message message2 = new Message();
		message2.setBlockAddr(blockAddr);
		message2.setData(controller.getOwnersList(blockAddr));
		message2.setUseless(true);
		// System.err.println("MSG IS FROM:" + msg.getFrom());
		// System.err.println("SENDING LIST  FROM TILE:" + tileId);
		// System.err.println("STATE OF BLOCK:"
		// + State.getStateName(controller.getBlockState(blockAddr)));
		message2.setIsData(false);
		message2.setType(MessageType.LISTREPLY);
		message2.setFrom(getTileId());
		message2.setTo(msg.getFrom());
		getBus().sendMessage(message2);

		controller.clearOwners(blockAddr);
	}

	//invalidate a block
	public void invalidate(Message msg, String addr) {
		l1Cache.changeBlockStatus(addr, State.INVALID);
		// send ACKNOWLEDGEMENT request message
		Message message = new Message();
		message.setBlockAddr(msg.getBlockAddr());
		message.setIsData(false);
		message.setType(MessageType.ACKNOWLEDGEMENT);
		message.setFrom(getTileId());
		message.setTo(msg.getFrom());
		getBus().sendMessage(message);
	}

	// This invalidate message comes from L2, does not need to reply an
	// acknowledgement
	public void blockInvalidate(Message msg, String addr) {
		if(l1Cache.isBlockCached(addr))
		l1Cache.changeBlockStatus(addr, State.INVALID);
	}

	public void replyInvalidList(Message msg, int timeCycle) {
		String msgOwner = msg.getData();
		if(msgOwner.equals("")) {
			//something wrong
			//redo
			currentTrace.setIssued(false,timeCycle);
			return;
		}
		ArrayList<Integer> ownerlist = controller
				.getOwnersListFromMsg(msgOwner);
		sharers = ownerlist;
		if (ownerlist.isEmpty()) {
			System.err.println("ERR!!!!!");
		}
		// send INVALIDATE message to ownerlist
		for (int i = 0; i < ownerlist.size(); i++) {
			int tile = ownerlist.get(i);
			if (tile == this.tileId) {
				for (int j = 0; j < sharers.size(); j++) {
					if (sharers.get(j) == tile) {
						sharers.remove(j);
					}
				}
				// sharers.remove(tile);
			} else {
				Message message = new Message();
				message.setBlockAddr(msg.getBlockAddr());
				message.setIsData(false);
				message.setType(MessageType.INVALIDATE);
				message.setFrom(getTileId());
				message.setTo(tile);
				getBus().sendMessage(message);
			}
		}

		// If WRITEHIT, change state to MODIFIED

		if (msg.isUseless() == true) {
			l1Cache.changeBlockStatus(msg.getBlockAddr(), State.MODIFIED);

			// If sharers is null, then send CHANGESTATE message to hometile and
			// set isfinish to true
			if (sharers.isEmpty()) {
				// send CHANGESTATE message to Hometile
				Message message = new Message();
				message.setBlockAddr(msg.getBlockAddr());
				message.setData("MODIFIED");
				message.setIsData(false);
				message.setType(MessageType.CHANGESTATE);
				message.setFrom(getTileId());
				message.setTo(AddrProcessor.processAddr(msg.getBlockAddr(),
						para.p, para.n2, para.a2, para.b).getTileId());
				getBus().sendMessage(message);

				// Finish the task
				l1Cache.updateTime(currentTrace.getAddr(), timeCycle);
				currentTrace.setFinished(true, timeCycle);

			}

		}

	}

	// receiving the request for block
	public boolean fetchBlock(Message msg) {
		String blockAddr = msg.getBlockAddr();

		if (!l1Cache.isBlockCached(blockAddr)) {
			// The address is supposed to exist in L1, however, the block might
			// be replaced, this means the data in L1 has already been written
			// back, so ask the requester of the block to do their task again
			Message message = new Message();
			message.setType(MessageType.REDOREQ);
			message.setIsData(false);
			message.setFrom(this.tileId);
			message.setTo(msg.getFrom());
			bus.sendMessage(message);
			return true;
		}

		if (msg.getData().equals("READ")) {
			l1Cache.changeBlockStatus(blockAddr, State.SHARED);
			// send BLOCKREPLY message
			Message message = new Message();
			message.setBlockAddr(blockAddr);
			message.setData(l1Cache.findBlock(blockAddr).getData());
			message.setIsData(true);
			message.setType(MessageType.BLOCKREPLY);
			message.setFrom(getTileId());
			message.setTo(msg.getFrom());
			getBus().sendMessage(message);

		} else if (msg.getData().equals("WRITE")) {
			l1Cache.writeBackData(l1Cache.findBlock(blockAddr), blockAddr, bus);
			l1Cache.changeBlockStatus(blockAddr, State.INVALID);
			// send BLOCKREPLY message
			Message message = new Message();
			message.setBlockAddr(blockAddr);
			message.setData(l1Cache.findBlock(blockAddr).getData());
			message.setIsData(true);
			message.setType(MessageType.BLOCKREPLY);
			message.setFrom(getTileId());
			message.setTo(msg.getFrom());
			getBus().sendMessage(message);

		}
		return true;
	}

	// receiving the reply of block from others
	public void replyBlock(Message msg, int timeCycle) {
		if(!msg.getBlockAddr().equals(currentTrace.getAddr())) return;
		String addr = msg.getBlockAddr();
		if (currentTrace.isTypeRead() == true) {
			// change l1 state to "shared"
			// l1Cache.addBlock(addr, timeCycle, msg.getData());
			l1Cache.receive(msg, timeCycle, bus);
			l1Cache.changeBlockStatus(addr, State.SHARED);
			if (msg.isUseless() == false) {
				// send CHANGESTATE message to Hometile
				Message message = new Message();
				message.setBlockAddr(addr);
				message.setIsData(false);
				message.setData("SHARED");
				message.setType(MessageType.CHANGESTATE);
				message.setFrom(getTileId());
				message.setTo(AddrProcessor.processAddr(addr, para.p, para.n2,
						para.a2, para.b).getTileId());
				getBus().sendMessage(message);
			}
		} else {
			// change l1 state to "modified"
			// l1Cache.addBlock(addr, timeCycle, msg.getData());
			l1Cache.receive(msg, timeCycle, bus);
			l1Cache.changeBlockStatus(addr, State.MODIFIED);

			// send CHANGESTATE message to Hometile
			Message message = new Message();
			message.setBlockAddr(addr);
			message.setIsData(false);
			message.setData("MODIFIED");
			message.setType(MessageType.CHANGESTATE);
			message.setFrom(getTileId());
			message.setTo(AddrProcessor.processAddr(addr, para.p, para.n2,
					para.a2, para.b).getTileId());
			getBus().sendMessage(message);
		}

		// Finish the task
		l1Cache.updateTime(currentTrace.getAddr(), timeCycle);
		currentTrace.setFinished(true, timeCycle);

		// Remove it later
		// try {
		// logger.log(currentTrace.toString());
		// } catch (IOException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
	}

	public void writeBack(Message msg, int timeCycle) {
		String blockAddr = msg.getBlockAddr();
		if (controller.isBlockCached(blockAddr)) {
			controller.modifyBlockData(blockAddr, msg.getData(), timeCycle);
		} else {
			// controller.addBlock(blockAddr, msg.getData(), timeCycle);
			controller.receiveBlock(blockAddr, msg.getData(), timeCycle, bus);
		}
		controller.removeOwner(blockAddr, msg.getFrom());
	}

	public void loadL2(Message msg, int timeCycle) {
		// controller.addBlock(msg.getBlockAddr(), msg.getData(), timeCycle);
		controller.receiveBlock(msg.getBlockAddr(), msg.getData(), timeCycle,
				bus);
	}

	public void sendRequestToL1(Message msg,int timeCycle) {
		String tileString = msg.getData();
		String[] tokens = tileString.split(",");
		if(tokens[0].trim().equals("")) {
			currentTrace.setIssued(false,timeCycle);
			return;
		}
		ArrayList<Integer> tileL1 = controller.getOwnersListFromMsg(tokens[0]
				.trim());
		String state = tokens[1].trim();
		if (state.equals("SHARED")) {
			if(l1Cache.isBlockCached(msg.getBlockAddr()))
			l1Cache.changeBlockStatus(msg.getBlockAddr(), State.SHARED);
		} else if (state.equals("INVALID")) {
			if(l1Cache.isBlockCached(msg.getBlockAddr()))
			l1Cache.changeBlockStatus(msg.getBlockAddr(), State.INVALID);
		}
		// send BLOCKFETCH message to L1
		Message message = new Message();
		message.setBlockAddr(msg.getBlockAddr());
		if (currentTrace.isTypeRead()) {
			message.setData("READ");
		} else {
			message.setData("WRITE");
		}
		message.setIsData(false);
		message.setType(MessageType.BLOCKFETCH);
		message.setFrom(getTileId());
		message.setTo(tileL1.get(0));
		getBus().sendMessage(message);
	}

	public void changeHomeState(Message msg, int timeCycle) {

		if (msg.getData().equals("SHARED")) {
			controller.changeBlockState(msg.getBlockAddr(), State.SHARED);
			controller.addOwner(msg.getBlockAddr(), msg.getFrom());
		} else if (msg.getData().equals("MODIFIED")) {
			controller.setBlockBusy(msg.getBlockAddr(), false);
			controller.changeBlockState(msg.getBlockAddr(), State.MODIFIED);
			controller.addOwner(msg.getBlockAddr(), msg.getFrom());
		}
	}

	// block is shared and sent from home node
	public void shareBlockReply(Message msg, int timeCycle) {
		String addr = msg.getBlockAddr();

		// if (currentTrace.isTypeRead() == true) {
		// // change l1 state to "shared"
		// l1Cache.addBlock(addr, timeCycle, msg.getData());
		// l1Cache.changeBlockStatus(addr, State.SHARED);
		//
		//
		// } else if (currentTrace.isTypeRead() == false) {
		// change l1 state to "modified"
		// l1Cache.addBlock(addr, timeCycle, msg.getData());
		l1Cache.receive(msg, timeCycle, bus);
		l1Cache.changeBlockStatus(addr, State.MODIFIED);

		// // send CHANGESTATE message to Hometile
		// Message message = new Message();
		// message.setBlockAddr(addr);
		// message.setData("MODIFIED");
		// message.setIsData(false);
		// message.setType(MessageType.CHANGESTATE);
		// message.setFrom(getTileId());
		// message.setTo(AddrProcessor.processAddr(addr, para.p, para.n2,
		// para.a2,
		// para.b).getTileId());
		// getBus().sendMessage(message);

	}

	public void receiveAck(Message msg, int timeCycle) {
		int tileId = msg.getFrom();
		for (int i = 0; i < sharers.size(); i++) {
			if (sharers.get(i) == tileId) {
				sharers.remove(i);
				break;
			}
		}
		if (sharers.isEmpty()) {
			// set task finished
			l1Cache.updateTime(currentTrace.getAddr(), timeCycle);
			currentTrace.setFinished(true, timeCycle);

			// Remove it later
			// try {
			// logger.log(currentTrace.toString());
			// } catch (IOException e) {
			// // TODO Auto-generated catch block
			// e.printStackTrace();
			// }

			// send CHANGESTATE message to Hometile
			Message message = new Message();
			message.setBlockAddr(msg.getBlockAddr());
			message.setIsData(false);
			message.setData("MODIFIED");
			message.setType(MessageType.CHANGESTATE);
			message.setFrom(getTileId());
			message.setTo(AddrProcessor.processAddr(msg.getBlockAddr(), para.p,
					para.n2, para.a2, para.b).getTileId());
			getBus().sendMessage(message);

		}
	}

	public Parameters getPara() {
		return para;
	}

	public void setPara(Parameters para) {
		this.para = para;
	}

	public ArrayList<Trace> getTraces() {
		return traces;
	}

	public void setTraces(ArrayList<Trace> traces) {
		this.traces = traces;
	}

	public L1Cache getL1Cache() {
		return l1Cache;
	}

	public void setL1Cache(L1Cache l1Cache) {
		this.l1Cache = l1Cache;
	}

	public L2Controller getController() {
		return controller;
	}

	public void setController(L2Controller controller) {
		this.controller = controller;
	}

	public int getTileId() {
		return tileId;
	}

	public void setTileId(int tileId) {
		this.tileId = tileId;
	}

	// issue commands or do nothing every cycle
	public void issue(int timeCycle) {
		if (currentTrace == null)
			return;
		// When current trace is finished, switch to the next available trace
		// If there are no such trace, currentTrace remains
		if (currentTrace.isFinished()) {
			if (traces.size() > 1) {
				int timeWait = traces.get(1).getCycle()
						- currentTrace.getCycle();
				if (timeWait == 0)
					timeWait = 1;
				Message msg = new Message();
				msg.setData(traces.get(1).toString());
				msg.setSystem(true);
				msg.setTimeOnRoad(timeWait);
				msg.setFrom(tileId);
				msg.setTo(tileId);
				msg.setType(MessageType.TRACEMSG);
				bus.sendMessage(msg);
				currentTrace = traces.get(1);
				currentTrace.setIssued(true, timeCycle + timeWait);
				traces.remove(0);
			}
		} else {
			// Or if it is the first trace added, it is not finished and not
			// issued
			if (!currentTrace.isIssued()) {
				Message msg = new Message();
				msg.setData(traces.get(0).toString());
				msg.setSystem(true);
				msg.setTimeOnRoad(0);
				msg.setFrom(tileId);
				msg.setTo(tileId);
				msg.setType(MessageType.TRACEMSG);
				bus.sendMessage(msg);
				currentTrace.setIssued(true, timeCycle);
			}	else {
				if(timeCycle-currentTrace.getIssuedTime()>500) currentTrace.setFinished(true, timeCycle);
			}
			
		}
	}
}
